#include "CCNotify.hh"
#include <sys/epoll.h>
#include <unistd.h>
#include <cstring>
#include <system_error>
#include <iostream>
using namespace wb;

#define THROW_SYSERR_(errmsg_) \
    throw std::system_error(errno, std::system_category(), errmsg_)

static const std::map<uint32_t, std::string> eventString = {
    {IN_ACCESS, "IN_ACCESS"},
    {IN_MODIFY, "IN_MODIFY"},
    {IN_ATTRIB, "IN_ATTRIB"},
    {IN_CLOSE_WRITE, "IN_CLOSE_WRITE"},
    {IN_CLOSE_NOWRITE, "IN_CLOSE_NOWRITE"},
    {IN_CLOSE, "IN_CLOSE"},
    {IN_OPEN, "IN_OPEN"},
    {IN_MOVED_FROM, "IN_MOVED_FROM"},
    {IN_MOVED_TO, "IN_MOVED_TO"},
    {IN_MOVE, "IN_MOVE"},
    {IN_CREATE, "IN_CREATE"},
    {IN_DELETE, "IN_DELETE"},
    {IN_DELETE_SELF, "IN_DELETE_SELF"},
    {IN_MOVE_SELF, "IN_MOVE_SELF"},
    {IN_ISDIR, "IN_ISDIR"},
};
static epoll_event events_[200];

std::string eventToString(uint32_t mask)
{
    std::string ret;
    for (uint32_t i = 1; i <= (1 << 30); i <<= 1)
    {
        if (i & mask)
        {
            ret += eventString.at(i);
            if (i != (1 << 30))
            {
                ret += " ";
            }
        }
    }
    return ret;
}

void DefaultCallback::operator()(inotify_event* e)
{
    std::cout << "Event: " << eventToString(e->mask) << " -- " << e->name
              << '\n';
    // std::cout << "Event: " << e->mask << " -- " << e->name << '\n';
}

CCNotify::CCNotify()
    : ifd_(inotify_init1(IN_CLOEXEC | IN_NONBLOCK)),
      efd_(epoll_create1(EPOLL_CLOEXEC))
{
    if (ifd_ < 0)
    {
        THROW_SYSERR_("inotify_init1() failed");
    }
    if (efd_ < 0)
    {
        THROW_SYSERR_("epoll_create1() failed");
    }
}

CCNotify::~CCNotify()
{
    if (ifd_ >= 0)
    {
        close(ifd_);
    }
    if (efd_ >= 0)
    {
        close(efd_);
    }
}

void CCNotify::WatchCtl(uint32_t eventMask,
                        std::string const& path,
                        callback_t cb,
                        uint32_t opt)
{
    switch (opt)
    {
        case kAdd:
        {
            int wfd = inotify_add_watch(ifd_, path.c_str(), eventMask);
            if (wfd < 0)
            {
                THROW_SYSERR_("inotify_add_watch() failed");
            }

            Event* e = new Event;
            epoll_event ev;
            ev.data.ptr = e;
            ev.events = EPOLLIN | EPOLLET;
            epoll_ctl(efd_, EPOLL_CTL_ADD, ifd_, &ev);

            e->cb = std::move(cb);
            e->path = path;
            e->wfd = wfd;
            e->event = eventMask;

            wfdMap_[path] = wfd;
        }
        break;
        case kMod:
        {
            // TODO
        }
        break;
        case kDel:
        {
            // TODO
        }
    }
}

std::vector<Event*> CCNotify::WatchList()
{
    std::vector<Event*> ret;
    // TODO
    return ret;
}

int CCNotify::Watch(std::vector<Event*>& events)
{
    int n = epoll_wait(efd_, events_, 200, 5000);
    if (n > 0)
    {
        for (int i = 0; i < n; ++i)
        {
            events.push_back(reinterpret_cast<Event*>(events_->data.ptr));
            int len = read(ifd_, eventBuffer_.data(), kEVENTBUFSIZE);
            if (len < 0)
            {
                THROW_SYSERR_("read() error in CCNotify::Watch");
            }
            int ind = 0;
            while (ind < len)
            {
                inotify_event* event =
                    reinterpret_cast<inotify_event*>(&eventBuffer_[ind]);
                if (event->mask & events.back()->event)
                {
                    events.back()->cb(event);
                }
                ind += kEVENTSIZE + event->len;
            }
        }
        // memset(events_, 0, n * sizeof(epoll_event));
    }
    return n;
}